# -*- coding: utf-8 -*-
import numpy as np 
import pandas as pd 

def load_simple_data():
    data_matrix = np.matrix([[1. ,2.1],
                             [2, 1.1],
                             [1.3, 1],
                             [1, 1],
                             [2, 1],])
    class_labels = np.matrix([1.0, 1.0, -1.0, -1.0, 1.0])
    return data_matrix, class_labels            # 返回的都是matrix，不需要在格式为matrix了

def stump_classify(data_matrix, dimen, thresh_val, thresh_ineq):
    ret_array = np.ones((np.shape(data_matrix)[0], 1))
    if thresh_ineq == 'lt':
        ret_array[data_matrix[:,dimen] <= thresh_val] = -1.0
    else:
        ret_array[data_matrix[:,dimen] > thresh_val] = -1.0
    return ret_array    


def build_stump(data_arr,class_labels,D):
    data_matrix = np.mat(data_arr)
    label_mat = np.mat(class_labels).T
    m,n = np.shape(data_matrix)
    num_steps = 10.0                        # 这个是定义的，在最大值最小值之间走多少步
    best_stump = {}
    best_class_est = np.mat(np.zeros((m,1)))
    # 初始化最小错误为正无穷，
    # 并且因为‘全局’变量（函数内的），所以在遍历0列和1列的时候，最小错误率是可以互相对比的
    min_error = np.inf                             
    for i in range(n):                          # 为啥要用列数？
        range_min = data_matrix[:,i].min()      # 用循环选出每列最小值和最大值
        range_max = data_matrix[:,i].max()      # 用循环选出每列最小值和最大值
        step_size = (range_max - range_min)/num_steps   # 步长
        for j in range(-1,int(num_steps)+1):    # 这的阈值其实范围是扩大的（头-1，尾+1），应为扩大不影响，
            for inequal in ['lt', 'gt']: # 看下是用'>'好，还是用‘<’划分好
                thresh_val = (range_min + float(j) * step_size)
                predicted_vals = stump_classify(data_matrix,i,thresh_val,inequal)#call stump classify with i, j, lessThan
                error_arr = np.mat(np.ones((m,1)))
                error_arr[predicted_vals == label_mat] = 0
                weighted_error = D.T*error_arr  # 因为已经格式化error_arr为全为1的每行1列，所以错的越多，数字越大
                #print "split: dim %d, thresh %.2f, thresh ineqal: %s, the weighted error is %.3f" % (i, thresh_val, inequal, weightedError)
                if weighted_error < min_error:
                    min_error = weighted_error
                    best_class_est = predicted_vals.copy()
                    # 表示 第i列，用thresh_val这个值划分，划分规则是inequal（大于或者小于）
                    best_stump['dim'] = i
                    best_stump['thresh'] = thresh_val
                    best_stump['ineq'] = inequal
    # best_stump详见上4行注释，min_error用此规则最小的错误率是多少,best_class_est为build_stump此方法的最佳分类结果
    return best_stump,min_error,best_class_est


#adaBoost算法
#@dataArr：数据矩阵
#@classLabels:标签向量
#@numIt:迭代次数    
def adaBoost_train_ds(data_arr,class_labels,num_it=40):        # ds为decision stump(单层决策树)
    '''
    @adaBoost算法
    @dataArr：数据矩阵
    @classLabels:标签向量
    @numIt:迭代次数  
    '''
    #弱分类器相关信息列表
    weak_class_arr=[]
    #获取数据集行数
    m=np.shape(data_arr)[0]
    #初始化权重向量的每一项值相等
    D=np.mat(np.ones((m,1))/m)
    #累计估计值向量
    agg_class_est=np.mat((m,1))
    #循环迭代次数
    for i in range(num_it):
        #根据当前数据集，标签及权重建立最佳单层决策树
        best_stump,error,class_est=build_stump(data_arr,class_labels,D)
        #打印权重向量
        print("D:",D.T)
        #求单层决策树的系数alpha,这个值是本次单层决策树在总分类器中占的权重
        # max(error,1e-16)其实就是error,主要就是为了防止是除数是0
        # alpha是error越小，alpha越大
        alpha=float(0.5*np.log((1.0-error)/(max(error,1e-16))))
        #存储决策树的系数alpha到字典
        best_stump['alpha']=alpha
        #将该决策树存入列表
        weak_class_arr.append(build_stump)
        #打印决策树的预测结果
        print("class_est:",class_est.T)
        #预测正确为exp(-alpha),预测错误为exp(alpha)
        #即增大分类错误样本的权重，减少分类正确的数据点权重
        # expon就像亦或,class_labels 和class_est符号相同的为正,只不过-1取反,结果就是符号相反的为正
        expon=np.multiply(-1*alpha*np.mat(class_labels).T,class_est)
        #更新权值向量
        D=np.multiply(D,np.exp(expon))  # 把权重 multiply,由于expon预测正确的为负值,就会减小权重
        D=D/D.sum()  # 为了D 元素相加还是=1
        #累加当前单层决策树的加权预测值
        agg_class_est = agg_class_est + alpha * class_est
        #agg_class_est = array(agg_class_est)
        print("agg_class_est",agg_class_est.T)
        #求出分类错的样本个数
        agg_errors=np.multiply(np.sign(agg_class_est)!=\
                    np.mat(class_labels).T,np.ones((m,1)))
        #计算错误率
        error_rate=agg_errors.sum()/m
        print("total error:",error_rate,"\n")
        #错误率为0.0退出循环
        if error_rate==0.0:
            break
    #返回弱分类器的组合列表
    return weak_class_arr

data_matrix, class_labels = load_simple_data()
classifier_array = adaBoost_train_ds(data_matrix, class_labels, num_it=20)


